home *** CD-ROM | disk | FTP | other *** search
Java Source | 1998-06-30 | 18.0 KB | 671 lines |
- /*
- * @(#)DefaultListSelectionModel.java 1.35 98/02/02
- *
- * Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved.
- *
- * This software is the confidential and proprietary information of Sun
- * Microsystems, Inc. ("Confidential Information"). You shall not
- * disclose such Confidential Information and shall use it only in
- * accordance with the terms of the license agreement you entered into
- * with Sun.
- *
- * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
- * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
- * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
- * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
- * THIS SOFTWARE OR ITS DERIVATIVES.
- *
- */
-
- package com.sun.java.swing;
-
- import java.util.EventListener;
- import java.util.BitSet;
- import java.io.Serializable;
-
- import com.sun.java.swing.event.*;
-
-
- /**
- * Default data model for list selections.
- * <p>
- * Warning: serialized objects of this class will not be compatible with
- * future swing releases. The current serialization support is appropriate
- * for short term storage or RMI between Swing1.0 applications. It will
- * not be possible to load serialized Swing1.0 objects with future releases
- * of Swing. The JDK1.2 release of Swing will be the compatibility
- * baseline for the serialized form of Swing objects.
- *
- * @version 1.35 02/02/98
- * @author Hans Muller
- * @author Philip Milne
- * @see ListSelectionModel
- */
-
- public class DefaultListSelectionModel implements ListSelectionModel, Cloneable, Serializable
- {
- private int selectionMode = MULTIPLE_INTERVAL_SELECTION;
- private int minIndex = -1;
- private int maxIndex = -1;
- private int anchorIndex = -1;
- private int leadIndex = -1;
- private int firstChangedIndex = -1;
- private int lastChangedIndex = -1;
- private boolean isAdjusting = false;
-
- protected BitSet value = new BitSet(32);
- protected EventListenerList listenerList = new EventListenerList();
-
- protected boolean leadAnchorNotificationEnabled = true;
-
- public int getMinSelectionIndex() { return minIndex; }
- public int getMaxSelectionIndex() { return maxIndex; }
-
- public boolean getValueIsAdjusting() { return isAdjusting; }
-
-
- public int getSelectionMode() { return selectionMode; }
-
- public void setSelectionMode(int selectionMode) {
- switch (selectionMode) {
- case SINGLE_SELECTION:
- case SINGLE_INTERVAL_SELECTION:
- case MULTIPLE_INTERVAL_SELECTION:
- this.selectionMode = selectionMode;
- break;
- default:
- throw new IllegalArgumentException("invalid selectionMode");
- }
- }
-
- public boolean isSelectedIndex(int index) {
- return ((index == -1) || (index < minIndex) || (index > maxIndex)) ? false : value.get(index);
- }
-
- public boolean isSelectionEmpty() {
- return (minIndex == -1) && (maxIndex == -1);
- }
-
- public void addListSelectionListener(ListSelectionListener l) {
- listenerList.add(ListSelectionListener.class, l);
- }
-
- public void removeListSelectionListener(ListSelectionListener l) {
- listenerList.remove(ListSelectionListener.class, l);
- }
-
-
- /**
- * Notify listeners that we are beginning or ending a
- * series of value changes
- */
- protected void fireValueChanged(boolean isAdjusting) {
- fireValueChanged(getMinSelectionIndex(), getMaxSelectionIndex(), isAdjusting);
- }
-
-
- /**
- * Notify ListSelectionListeners that the value of the selection,
- * in the closed interval firstIndex,lastIndex, has changed.
- */
- protected void fireValueChanged(int firstIndex, int lastIndex) {
- fireValueChanged(firstIndex, lastIndex, getValueIsAdjusting());
- }
-
-
- /**
- * @param firstIndex The first index in the interval.
- * @param index1 The last index in the interval.
- * @param isAdjusting True if this is the final change in a series of them.
- * @see EventListenerList
- */
- protected void fireValueChanged(int firstIndex, int lastIndex, boolean isAdjusting)
- {
- Object[] listeners = listenerList.getListenerList();
- ListSelectionEvent e = null;
-
- for (int i = listeners.length - 2; i >= 0; i -= 2) {
- if (listeners[i] == ListSelectionListener.class) {
- if (e == null) {
- e = new ListSelectionEvent(this, firstIndex, lastIndex, isAdjusting);
- }
- ((ListSelectionListener)listeners[i+1]).valueChanged(e);
- }
- }
- }
-
-
- public void clearSelection()
- {
- if ((minIndex == -1) || (maxIndex == -1)) {
- return;
- }
-
- boolean selectionChanged = false;
-
- for(int r = minIndex; r <= maxIndex; r++) {
- if (value.get(r)) {
- selectionChanged = true;
- }
- value.clear(r);
- }
-
- minIndex = maxIndex = anchorIndex = leadIndex = -1;
-
- if (selectionChanged) {
- fireValueChanged(minIndex, maxIndex);
- }
- }
-
- /**
- * Sets the value of the leadAnchorNotificationEnabled flag.
- * @see #isLeadAnchorNotificationEnabled()
- */
- public void setLeadAnchorNotificationEnabled(boolean flag) {
- leadAnchorNotificationEnabled = flag;
- }
-
- /**
- * Returns the value of the leadAnchorNotificationEnabled flag.
- * When leadAnchorNotificationEnabled is true the model
- * generates notification events with bounds that cover all the changes to
- * the selection plus the changes to the lead and anchor indices.
- * Setting the flag to false causes a norrowing of the event's bounds to
- * include only the elements that have been selected or deselected since
- * the last change. Either way, the model continues to maintain the lead
- * and anchor variables internally. The default is true.
- * @return the value of the leadAnchorNotificationEnabled flag
- * @see #setLeadAnchorNotificationEnabled(boolean)
- */
- /* The JTable uses this to optimise row selection on mouse dragged.
- * Setting the flag to false causes the repainting code to only redraw
- * those rows that have been selected (or deselected) since the last event.
- */
- public boolean isLeadAnchorNotificationEnabled() {
- return leadAnchorNotificationEnabled;
- }
-
- /* If the lead or anchor indices have changed, add them to
- * the firstChanged/lastChanged interval.
- */
- private void updateLeadAnchorIndices(int index0, int index1)
- {
- if (leadAnchorNotificationEnabled) {
- if ((anchorIndex != -1) && (anchorIndex != index0)) {
- int minAnchorIndex = Math.min(anchorIndex, index0);
- int maxAnchorIndex = Math.max(anchorIndex, index0);
-
- firstChangedIndex = (firstChangedIndex == -1)
- ? maxAnchorIndex
- : Math.min(minAnchorIndex, firstChangedIndex);
- lastChangedIndex = Math.max(maxAnchorIndex, lastChangedIndex);
- }
-
- if ((leadIndex != -1) && (leadIndex != index1)) {
- int minLeadIndex = Math.min(leadIndex, index1);
- int maxLeadIndex = Math.max(leadIndex, index1);
-
- firstChangedIndex = (firstChangedIndex == -1)
- ? minLeadIndex
- : Math.min(minLeadIndex, firstChangedIndex);
- lastChangedIndex = Math.max(maxLeadIndex, lastChangedIndex);
- }
- }
-
- anchorIndex = index0;
- leadIndex = index1;
- }
-
-
- public void setSelectionInterval(int index0, int index1)
- {
- if (getSelectionMode() == SINGLE_SELECTION) {
- index0 = index1;
- }
-
- int newMinIndex, newMaxIndex;
-
- /* Special case, clear selection.
- */
-
- if ((index0 == -1) && (index1 == -1)) {
- clearSelection();
- return;
- }
-
- newMinIndex = Math.min(index0, index1);
- newMaxIndex = Math.max(index0, index1);
-
- /* Union newMinIndex,newMaxIndex with minIndex,maxIndex.
- */
-
- if ((minIndex == -1) || (newMinIndex < minIndex)) {
- minIndex = newMinIndex;
- }
-
- if ((maxIndex == -1) || (newMaxIndex > maxIndex)) {
- maxIndex = newMaxIndex;
- }
-
- /* Update the selection value bitset and keep track of the
- * first and last indices that were actually changed.
- */
-
- firstChangedIndex = -1;
- lastChangedIndex = -1;
-
- for(int r = minIndex; r < newMinIndex; r++) {
- if (value.get(r)) {
- if (firstChangedIndex == -1) {
- firstChangedIndex = lastChangedIndex = r;
- }
- else if (r > lastChangedIndex) {
- lastChangedIndex = r;
- }
- }
- value.clear(r);
- }
-
- for(int r = newMinIndex; r <= newMaxIndex; r++) {
-
- if (!value.get(r)) {
- if (firstChangedIndex == -1) {
- firstChangedIndex = lastChangedIndex = r;
- }
- else if (r > lastChangedIndex) {
- lastChangedIndex = r;
- }
- }
- value.set(r);
- }
-
- for(int r = newMaxIndex + 1; r <= maxIndex; r++) {
- if (value.get(r)) {
- if (firstChangedIndex == -1) {
- firstChangedIndex = lastChangedIndex = r;
- }
- else if (r > lastChangedIndex) {
- lastChangedIndex = r;
- }
- }
- value.clear(r);
- }
-
- updateLeadAnchorIndices(index0, index1);
-
- minIndex = newMinIndex;
- maxIndex = newMaxIndex;
-
- if ((firstChangedIndex != -1) && (lastChangedIndex != -1)) {
- fireValueChanged(firstChangedIndex, lastChangedIndex);
- }
- }
-
-
- public void addSelectionInterval(int index0, int index1)
- {
- int mode = getSelectionMode();
- if ((mode == SINGLE_SELECTION) || (mode == SINGLE_INTERVAL_SELECTION)) {
- setSelectionInterval(index0, index1);
- return;
- }
-
- if ((index0 == -1) && (index1 == -1)) {
- return;
- }
-
- int addMinIndex = Math.min(index0, index1);
- int addMaxIndex = Math.max(index0, index1);
-
- /* Update minIndex, maxIndex.
- */
-
- if ((minIndex == -1) || (addMinIndex < minIndex)) {
- minIndex = addMinIndex;
- }
-
- if ((maxIndex == -1) || (addMaxIndex > maxIndex)) {
- maxIndex = addMaxIndex;
- }
-
- /* Update the selection value bitset, and keep track of the
- * first and last indices that were actually changed.
- */
-
- firstChangedIndex = -1;
- lastChangedIndex = -1;
-
- for(int r = addMinIndex; r <= addMaxIndex; r++) {
- if (!value.get(r)) {
- if (firstChangedIndex == -1) {
- firstChangedIndex = lastChangedIndex = r;
- }
- else if (r > lastChangedIndex) {
- lastChangedIndex = r;
- }
- value.set(r);
- }
- }
-
- updateLeadAnchorIndices(index0, index1);
-
- if ((firstChangedIndex != -1) && (lastChangedIndex != -1)) {
- fireValueChanged(firstChangedIndex, lastChangedIndex);
- }
-
- }
-
-
- public void removeSelectionInterval(int index0, int index1)
- {
- /* No-op special case, or selection is already empty.
- */
-
- if ((index0 == -1) && (index1 == -1)) {
- return;
- }
-
- /* Current selection is empty
- */
-
- if ((minIndex == -1) && (maxIndex == -1)) {
- return;
- }
-
- /* Update the selection value bitset, and keep track of the
- * first and last indices that were actually changed.
- */
-
- int remMinIndex = Math.min(index0, index1);
- int remMaxIndex = Math.max(index0, index1);
-
- int firstChangedIndex = -1;
- int lastChangedIndex = -1;
-
- for(int r = remMinIndex; r <= remMaxIndex; r++) {
- if (value.get(r)) {
- if (firstChangedIndex == -1) {
- firstChangedIndex = lastChangedIndex = r;
- }
- else if (r > lastChangedIndex) {
- lastChangedIndex = r;
- }
- value.clear(r);
- }
- }
-
- /* Find the new min and max index values.
- */
-
- int newMinIndex = -1;
- int newMaxIndex = -1;
-
- for(int r = minIndex; r <= maxIndex; r++) {
- if (value.get(r)) {
- if (newMinIndex == -1) {
- newMinIndex = newMaxIndex = r;
- }
- else if (r > newMaxIndex) {
- newMaxIndex = r;
- }
- }
- }
-
- minIndex = newMinIndex;
- maxIndex = newMaxIndex;
-
- if ((firstChangedIndex != -1) && (lastChangedIndex != -1)) {
- fireValueChanged(firstChangedIndex, lastChangedIndex);
- }
- }
-
-
- /**
- * Insert length indices beginning before/after index. This is typically
- * called to sync the selection model with a corresponding change
- * in the data model.
- */
-
- public void insertIndexInterval(int index, int length, boolean before)
- {
- /* The first new index will appear at insMinIndex and the last
- * one will appear at insMaxIndex
- */
- int insMinIndex = (before) ? Math.max(0, index - 1) : index + 1;
- int insMaxIndex = (insMinIndex + length) - 1;
-
- /* If both of the indices next to the insertion point were
- * selected then select all of the new indices. Special case:
- * if you're inserting before index 0 then new indices will
- * be selected if the index 0 is selected.
- */
- boolean selected = value.get(index) && value.get(insMinIndex);
-
- /* Right shift the entire bitset by length, beginning with
- * index-1 if before is true, index+1 if it's false (i.e. with
- * insMinIndex).
- */
- for(int i = maxIndex; i >= insMinIndex; i--) {
- if (value.get(i)) {
- value.set(i + length);
- }
- else {
- value.clear(i + length);
- }
- }
-
- /* Initialize the newly inserted indices.
- */
- for(int i = insMinIndex; i <= insMaxIndex; i++) {
- if (selected) {
- value.set(i);
- }
- else {
- value.clear(i);
- }
- }
-
- if (minIndex != -1 && (minIndex > insMinIndex || (minIndex == insMinIndex && !selected))) {
- minIndex += length;
- }
-
- if (maxIndex != -1 && maxIndex >= insMinIndex) {
- maxIndex += length;
- }
-
- fireValueChanged(insMinIndex, maxIndex);
- }
-
-
- /**
- * Remove the indices in the interval index0,index1 (inclusive) from
- * the selection model. This is typically called to sync the selection
- * model width a corresponding change in the data model. Note
- * that (as always) index0 need not be <= index1.
- */
- public void removeIndexInterval(int index0, int index1)
- {
- /* Special case - empty interval.
- */
- if ((index0 < 0) || (index1 < 0)) {
- return;
- }
-
- /* Special case - nothing's selected.
- */
- if ((minIndex == -1) && (maxIndex == -1)) {
- return;
- }
-
- int rmMinIndex = Math.min(index0, index1);
- int rmMaxIndex = Math.max(index0, index1);
- int gapLength = (rmMaxIndex - rmMinIndex) + 1;
-
- /* Special case - we're not removing any selected indices.
- */
- if ((rmMinIndex > maxIndex) || (rmMaxIndex < minIndex)) {
- return;
- }
-
- /* Special case - we're removing all of the selected indices.
- */
- if ((rmMinIndex <= minIndex) && (rmMaxIndex >= maxIndex)) {
- clearSelection();
- return;
- }
-
- /* Shift the entire bitset to the left to close the index0, index1
- * gap. Clear the bits to the right of the old maxIndex.
- */
- for(int i = rmMinIndex; i <= rmMaxIndex; i++) {
- if (value.get(i + gapLength)) {
- value.set(i);
- }
- else {
- value.clear(i);
- }
- }
-
- for(int i = maxIndex; i > maxIndex - gapLength; i--) {
- value.clear(i);
- }
-
- /* Compute the new min and max selection indices.
- */
-
- int oldMinIndex = minIndex;
- int oldMaxIndex = maxIndex;
-
- if (oldMinIndex >= rmMinIndex) {
- minIndex = -1;
- for(int i = rmMinIndex; i <= oldMaxIndex; i++) {
- if (value.get(i)) {
- minIndex = i;
- break;
- }
- }
- }
-
- if (minIndex == -1) {
- maxIndex = -1;
- }
- else if (oldMaxIndex > rmMaxIndex) {
- maxIndex = oldMaxIndex - gapLength;
- }
- else {
- for(int i = oldMaxIndex; i >= minIndex; i--) {
- if (value.get(i)) {
- maxIndex = i;
- break;
- }
- }
- }
-
- if ((maxIndex == -1) && (minIndex == -1)) {
- fireValueChanged(rmMinIndex, rmMaxIndex);
- }
- else {
- firstChangedIndex = Math.min(rmMinIndex, minIndex);
- lastChangedIndex = Math.max(rmMaxIndex, maxIndex);
- fireValueChanged(firstChangedIndex, lastChangedIndex);
- }
- }
-
-
- public void setValueIsAdjusting(boolean b) {
- if (b != this.isAdjusting) {
- this.isAdjusting = b;
- this.fireValueChanged(b);
- }
- }
-
-
- public String toString() {
- String s = ((getValueIsAdjusting()) ? "~" : "=") + value.toString();
- return getClass().getName() + " " + Integer.toString(hashCode()) + " " + s;
- }
-
- /**
- * Returns a clone of the reciever with the same selection.
- * listenerLists are not duplicated.
- *
- * @exception CloneNotSupportedException if the receiver does not
- * both (a) implement the Cloneable interface and (b) define a
- * <code>clone</code> method.
- */
- public Object clone() throws CloneNotSupportedException {
- DefaultListSelectionModel clone = (DefaultListSelectionModel)super.clone();
- clone.value = (BitSet)value.clone();
- clone.listenerList = new EventListenerList();
- return clone;
- }
-
- public int getAnchorSelectionIndex() {
- return anchorIndex;
- }
-
- public int getLeadSelectionIndex() {
- return leadIndex;
- }
-
- public void setAnchorSelectionIndex(int index) {
- anchorIndex = index;
- }
-
- public void setLeadSelectionIndex(int index) {
- updateLead(index);
- leadIndex = index;
- }
-
- //
- // Private methods.
- //
-
- // Return true if the index lies in the range [a, b).
- private boolean contains(int a, int b, int index) {
- if (a <= b)
- return (index >= a) && (index < b);
- else
- return (index <= a) && (index > b);
- }
-
- private int sign(int a, int b) {
- return (a < b) ? -1 : (a > b) ? 1 : 0;
- }
-
- // Now that manipulation of the lead is part of the
- // selection model itself, an easier way to perform these
- // operations is to temporarily disable the event posting and
- // always do the operation in two stages - a deselection
- // followed by a selection. Do that in the next release.
- private void updateLead(int index) {
- if (anchorIndex == -1) {
- anchorIndex = index;
- }
-
- // boolean deselect = !isSelectedIndex(anchorIndex);
- boolean deselect = false;
-
- // This is an edge case were the anchor is straddled by the new and old
- // lead indices. This would happen if a user were to shift click on
- // the opposite side of the anchor or move the mouse
- // very quickly from one side of the anchor to the other.
- // To update the selection we need to take two steps here,
- // one to deselect the old area and one to select the new.
- if (!deselect && contains(leadIndex, index, anchorIndex)) {
- removeSelectionInterval(anchorIndex, leadIndex);
- addSelectionInterval(anchorIndex, index);
- }
-
- if (!deselect && !contains(anchorIndex, leadIndex, index)) {
- addSelectionInterval(anchorIndex, index);
- }
- else {
- int delta = sign(anchorIndex, leadIndex);
- removeSelectionInterval(index - delta, leadIndex);
- }
- }
- }
-
-